let b2Vec2 = Box2D.Common.Math.b2Vec2;
let b2BodyDef = Box2D.Dynamics.b2BodyDef;
let b2Body = Box2D.Dynamics.b2Body;
let b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
let b2World = Box2D.Dynamics.b2World;
let b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
let b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
let b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
let b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef;

let world;
// 浏览器中的 30px 对应 world 中的 1m
const scale = 30;
let context;
function init() {
	const gravity = new b2Vec2(0, 9.5);
	const allowSleep = true;
	world = new b2World(gravity, allowSleep);

	createFloor();
	createRectangularBody();
	createCircularBody();
	createSimplePolygonBody();

	createComplexBody();

	createRevoluteJoint();

	createSpecialBody();

	listenForContact();

	setupDebugDraw();
	animate();
}
function createFloor() {
	let bodyDef = new b2BodyDef;
	bodyDef.type = b2Body.b2_staticBody;
	bodyDef.position.x = 640 / 2 / scale;
	bodyDef.position.y = 450 / scale;

	let fixtureDef = new b2FixtureDef;
	fixtureDef.density = 1.0;
	fixtureDef.friction = .5;
	fixtureDef.restitution = .2;

	fixtureDef.shape = new b2PolygonShape;
	// 不同于 canvas 以左上角为基准点，SetAsBox() 是以形状的中心为基准点的
	// 所以这里画出了 640px 20px 的矩形
	fixtureDef.shape.SetAsBox(320 / scale, 10 / scale);

	let body = world.CreateBody(bodyDef);
	let fixture = body.CreateFixture(fixtureDef);
}
function setupDebugDraw() {
	context = document.getElementById('canvas').getContext('2d');
	let debugDraw = new b2DebugDraw();

	debugDraw.SetSprite(context);
	debugDraw.SetDrawScale(scale);
	debugDraw.SetFillAlpha(.3);
	debugDraw.SetLineThickness(1);
	debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);

	world.SetDebugDraw(debugDraw);
}
const timeStep = 1 / 60;

//As per the Box2d manual, the suggested iteration count for Box2D is 8 for velocity and 3 for position.
const velocityIterations = 8;
const positionIterations = 3;

function animate() {
    // 传入时间步长，速度，位置迭代数
	world.Step(timeStep, velocityIterations, positionIterations);
	world.ClearForces();

	world.DrawDebugData();

	// Custom Drawing
	if (specialBody) {
	    drawSpecialBody();
	}

	//Kill Special Body if Dead
	if (specialBody && specialBody.GetUserData().life <= 0) {
	    world.DestroyBody(specialBody);
	    specialBody = undefined;
	    console.log("The special body was destroyed");
	}

	setTimeout(animate, timeStep);
}
function createRectangularBody() {
	var bodyDef = new b2BodyDef;

	bodyDef.type = b2Body.b2_dynamicBody;
	// 在画布中的位置
	bodyDef.position.x = 40 / scale;
	bodyDef.position.y = 100 / scale;

	var fixtureDef = new b2FixtureDef;

	fixtureDef.density = 1.0;
	fixtureDef.friction = 0.5;
	fixtureDef.restitution = 0.3;
	// 形状为 60px 宽 100px 高的矩形
	fixtureDef.shape = new b2PolygonShape;
	fixtureDef.shape.SetAsBox(30 / scale, 50 / scale);

	var body = world.CreateBody(bodyDef);
	var fixture = body.CreateFixture(fixtureDef);
}
function createCircularBody() {
	var bodyDef = new b2BodyDef;

	bodyDef.type = b2Body.b2_dynamicBody;
	bodyDef.position.x = 130 / scale;
	bodyDef.position.y = 100 / scale;

	var fixtureDef = new b2FixtureDef;

	fixtureDef.density = 1.0;
	fixtureDef.friction = 0.5;
	fixtureDef.restitution = 0.7;
	// 圆的半径 30px
	fixtureDef.shape = new b2CircleShape(30 / scale);

	var body = world.CreateBody(bodyDef);
	var fixture = body.CreateFixture(fixtureDef);
}
function createSimplePolygonBody() {
	var bodyDef = new b2BodyDef;

	bodyDef.type = b2Body.b2_dynamicBody;
	bodyDef.position.x = 230 / scale;
	bodyDef.position.y = 50 / scale;

	var fixtureDef = new b2FixtureDef;

	fixtureDef.density = 1.0;
	fixtureDef.friction = 0.5;
	fixtureDef.restitution = 0.6;

	fixtureDef.shape = new b2PolygonShape;
	// 注意：顶点坐标都是相对于物体原点的，即绘制位置原点 0 0 即 230 50
	// 必须顺时针方向定义各个顶点坐标
	// Create an array of b2Vec2 points in clockwise direction
	var points = [
		new b2Vec2(0, 0),
		new b2Vec2(40 / scale, 50 / scale),
		new b2Vec2(50 / scale, 100 / scale),
		new b2Vec2(-50 / scale, 100 / scale),
		new b2Vec2(-40 / scale, 50 / scale),

	];

	// Use SetAsArray to define the shape using the points array
	fixtureDef.shape.SetAsArray(points, points.length);

	var body = world.CreateBody(bodyDef);

	var fixture = body.CreateFixture(fixtureDef);

}
function createComplexBody() {
    var bodyDef = new b2BodyDef;

    bodyDef.type = b2Body.b2_dynamicBody;
    bodyDef.position.x = 350 / scale;
    bodyDef.position.y = 50 / scale;
    var body = world.CreateBody(bodyDef);

    //Create first fixture and attach a circular shape to the body
    var fixtureDef = new b2FixtureDef;

    fixtureDef.density = 1.0;
    fixtureDef.friction = 0.5;
    fixtureDef.restitution = 0.7;
    fixtureDef.shape = new b2CircleShape(40 / scale);
    body.CreateFixture(fixtureDef);

    // Create second fixture and attach a polygon shape to the body.
    fixtureDef.shape = new b2PolygonShape;
    var points = [
        new b2Vec2(0, 0),
        new b2Vec2(40 / scale, 50 / scale),
        new b2Vec2(50 / scale, 100 / scale),
        new b2Vec2(-50 / scale, 100 / scale),
        new b2Vec2(-40 / scale, 50 / scale),
    ];

    fixtureDef.shape.SetAsArray(points, points.length);
    body.CreateFixture(fixtureDef);
}
function createRevoluteJoint() {
    //Define the first body
    var bodyDef1 = new b2BodyDef;

    bodyDef1.type = b2Body.b2_dynamicBody;
    bodyDef1.position.x = 480 / scale;
    bodyDef1.position.y = 50 / scale;
    var body1 = world.CreateBody(bodyDef1);

    //Create first fixture and attach a rectangular shape to the body
    var fixtureDef1 = new b2FixtureDef;

    fixtureDef1.density = 1.0;
    fixtureDef1.friction = 0.5;
    fixtureDef1.restitution = 0.5;
    fixtureDef1.shape = new b2PolygonShape;
    fixtureDef1.shape.SetAsBox(50 / scale, 10 / scale);

    body1.CreateFixture(fixtureDef1);

    // Define the second body
    var bodyDef2 = new b2BodyDef;

    bodyDef2.type = b2Body.b2_dynamicBody;
    bodyDef2.position.x = 470 / scale;
    bodyDef2.position.y = 50 / scale;
    var body2 = world.CreateBody(bodyDef2);

    //Create second fixture and attach a polygon shape to the body
    var fixtureDef2 = new b2FixtureDef;

    fixtureDef2.density = 1.0;
    fixtureDef2.friction = 0.5;
    fixtureDef2.restitution = 0.5;
    fixtureDef2.shape = new b2PolygonShape;
    var points = [
        new b2Vec2(0, 0),
        new b2Vec2(40 / scale, 50 / scale),
        new b2Vec2(50 / scale, 100 / scale),
        new b2Vec2(-50 / scale, 100 / scale),
        new b2Vec2(-40 / scale, 50 / scale),
    ];

    fixtureDef2.shape.SetAsArray(points, points.length);
    body2.CreateFixture(fixtureDef2);


    // Create a joint between body1 and body2
    var jointDef = new b2RevoluteJointDef;
    var jointCenter = new b2Vec2(470 / scale, 50 / scale);

    jointDef.Initialize(body1, body2, jointCenter);
    world.CreateJoint(jointDef);
}
let specialBody;

function createSpecialBody() {
    var bodyDef = new b2BodyDef;

    bodyDef.type = b2Body.b2_dynamicBody;
    bodyDef.position.x = 450 / scale;
    bodyDef.position.y = 0 / scale;

    specialBody = world.CreateBody(bodyDef);
    specialBody.SetUserData({ name: "special", life: 250 });

    //Create a fixture to attach a circular shape to the body
    var fixtureDef = new b2FixtureDef;

    fixtureDef.density = 1.0;
    fixtureDef.friction = 0.5;
    fixtureDef.restitution = 0.5;

    fixtureDef.shape = new b2CircleShape(30 / scale);

    var fixture = specialBody.CreateFixture(fixtureDef);
}
function listenForContact() {
	var listener = new Box2D.Dynamics.b2ContactListener;
	// 求解器就是判断物体是否接触
	// 求解器完成后调用，就是碰撞时调用 BeginContact() 开始接触时调用 EndContact 结束接触时调用 PreSolve 求解器求解前调用
	listener.PostSolve = function (contact, impulse) {
		// 提供碰撞后的冲击力
        var body1 = contact.GetFixtureA().GetBody();
        var body2 = contact.GetFixtureB().GetBody();

        // If either of the bodies is the special body, reduce its life
        if (body1 == specialBody || body2 == specialBody) {
            var impulseAlongNormal = impulse.normalImpulses[0];

            specialBody.GetUserData().life -= impulseAlongNormal;
            console.log("The special body was in a collision with impulse", impulseAlongNormal, "and its life has now become ", specialBody.GetUserData().life);
        }
    };
    world.SetContactListener(listener);
}

function drawSpecialBody() {
    // Get body position and angle
    var position = specialBody.GetPosition();
    var angle = specialBody.GetAngle();

    // Translate and rotate axis to body position and angle
    context.translate(position.x * scale, position.y * scale);
    context.rotate(angle);

    // Draw a filled circular face
    context.fillStyle = "rgb(200, 150, 250)";
    context.beginPath();
    context.arc(0, 0, 30, 0, 2 * Math.PI, false);
    context.fill();

    // Draw two rectangular eyes
    context.fillStyle = "rgb(255, 255, 255)";
    context.fillRect(-15, -15, 10, 5);
    context.fillRect(5, -15, 10, 5);

    // Draw an upward or downward arc for a smile depending on life
    context.strokeStyle = "rgb(255, 255, 255)";
    context.beginPath();
    if (specialBody.GetUserData().life > 100) {
        context.arc(0, 0, 10, Math.PI, 2 * Math.PI, true);
    } else {
        context.arc(0, 10, 10, Math.PI, 2 * Math.PI, false);
    }
    context.stroke();

    // Translate and rotate axis back to original position and angle
    context.rotate(-angle);
    context.translate(-position.x * scale, -position.y * scale);
}